﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Quail;
using Quail.Helpers;


namespace QuailRepositoryManager
{
    /// <summary>
    /// Implements the main form for the Quail Repository Manager application
    /// </summary>
    public partial class FormMain : Form
    {
        private readonly SelectorGeneratorFactory _selectorFactory = new SelectorGeneratorFactory();
        private readonly QuailRepoTestShell _testShell;
        private readonly List<string> _tempFiles = new List<string>();
        private readonly AutoCompleteStringCollection _autocomplete = new AutoCompleteStringCollection();
        private readonly object _fakeNodeTag = new object();
        private DataTable _dtSelectors;
        
        /// <summary>
        /// Initializes a new instance of the FormMain class
        /// </summary>
        public FormMain()
        {
            InitializeComponent();
            _testShell = new QuailRepoTestShell(this);
        }

        private QuailRepoTestShell TestShell
        {
            get
            {
                return _testShell;
            }
        }

        private void FormMain_Load(object sender, EventArgs e)
        {
            _dtSelectors = dsSelectors.Tables[0];

            AcceptButton = ButtonOpen;

            TextBoxSelector.AutoCompleteCustomSource = _autocomplete;

            var urls = Properties.Settings.Default.URLs;

            TabControlInspectors.SelectedIndex = Properties.Settings.Default.TabIndex;
            Location = Properties.Settings.Default.Location;
            Size = Properties.Settings.Default.Size;
            SplitContainer1.SplitterDistance = Properties.Settings.Default.Splitter;

            if (urls != null)
            {
                foreach (var url in urls)
                    ComboBoxUrl.Items.Add(url);
            }

            if (ComboBoxUrl.Items.Count > 0)
                ComboBoxUrl.SelectedIndex = 0;
            else
                ComboBoxUrl.Text = "http://qas.snagajob.com";
        }

        private void FormMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (string file in _tempFiles)
            {
                try
                {
                    System.IO.File.Delete(file);
                }
                catch
                {
                }
            }

            _tempFiles.Clear();
            _testShell.Stop();
            _testShell.Dispose();

            Properties.Settings.Default.TabIndex = TabControlInspectors.SelectedIndex;
            Properties.Settings.Default.Location = Location;
            Properties.Settings.Default.Size = Size;
            Properties.Settings.Default.Splitter = SplitContainer1.SplitterDistance;
            Properties.Settings.Default.Save();
        }

        private void btnRefresh_Click(object sender, EventArgs e)
        {
            RefreshSelectorGrid();
        }

        /*private void ButtonFind_Click(object sender, EventArgs e)
        {
            // search through the nodes for something that might match the text
            string texttofind = TextBoxSearch.Text;

            if (texttofind.Length == 0)
            {
                MessageBox.Show(this, "Please enter something to look for!", "Oops");
                TextBoxSearch.Focus();
                return;
            }

            WaitCursor wait = new WaitCursor();

            Regex regex = new Regex(
                texttofind,
                RegexOptions.Compiled | RegexOptions.IgnoreCase);

            bool found;
            if (TreeViewElements.SelectedNode == null)
            {
                if (TreeViewElements.Nodes.Count >= 0)
                {
                    if (IsMatchingNode(TreeViewElements.Nodes[0], regex))
                        found = true;
                    else
                        found = SearchForNextNode(TreeViewElements.Nodes[0], regex);
                }
                else
                    found = false;
            }
            else
                found = SearchForNextNode(TreeViewElements.SelectedNode, regex);

            wait.Restore();

            if (!found)
                MessageBox.Show(this, "No matching node found!", "Not found", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }*/

        private void ButtonOpen_Click(object sender, EventArgs e)
        {
            OpenUrl(ComboBoxUrl.Text);
        }

        private void ButtonRefresh_Click(object sender, EventArgs e)
        {
            SyncForm();
        }

        private void ButtonShow_Click(object sender, EventArgs e)
        {
            ShowSelector();
        }

        private void ComboBoxUrl_Enter(object sender, EventArgs e)
        {
            AcceptButton = ButtonOpen;
        }

        private void ComboBoxUrl_Leave(object sender, EventArgs e)
        {
            if (AcceptButton == ButtonOpen)
                AcceptButton = null;
        }

        private void dgSelectors_SelectionChanged(object sender, EventArgs e)
        {
            TextBoxSelector.Text = dgSelectors.SelectedRows[0].Cells[1].Value.ToString();
        }

        private void jQuerySelectorSyntaxToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Process.Start("http://docs.jquery.com/Selectors");
        }

        private void logToolStripMenuItem_Click(object sender, EventArgs e)
        {
            tabLog.Select();
        }

        private void TextBoxSelector_Enter(object sender, EventArgs e)
        {
            AcceptButton = ButtonShow;
        }

        private void TextBoxSelector_Leave(object sender, EventArgs e)
        {
            if (AcceptButton == ButtonShow)
                AcceptButton = null;
        }

        private void ToolStripMenuItemAbout_Click(object sender, EventArgs e)
        {
            MessageBox.Show(this, "Quail Repository Manager - Written by John Gonyo", "About");
        }

        private void ToolStripMenuItemElementInspector_Click(object sender, EventArgs e)
        {
            tabInspector.Select();
        }


        private void ToolStripMenuItemRestartSelenium_Click(object sender, EventArgs e)
        {
            _testShell.Stop();
            ButtonOpen_Click(sender, e);
        }

        private void ToolStripMenuItemExit_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void ToolStripMenuItemRefresh_Click(object sender, EventArgs e)
        {
            SyncForm();
        }

        private void TreeViewElements_AfterSelect(object sender, TreeViewEventArgs e)
        {
            FillInDetails(e.Node);
            ShowNode(e.Node);
        }

        private void TreeViewElements_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            FillNode(e.Node);
        }

        private void AddControlsForSelector(string selector, IEnumerable<WebControl> controls)
        {
            TreeNodeCollection parentNodes = null;

            foreach (TreeNode node in TreeViewElements.Nodes)
            {
                if (node.Text == selector)
                {
                    parentNodes = node.Nodes;
                    parentNodes.Clear();
                    break;
                }
            }

            if (parentNodes == null)
            {
                var tn = new TreeNode {Text = selector};
                TreeViewElements.Nodes.Add(tn);
                parentNodes = tn.Nodes;
            }

            AddControls(parentNodes, controls);
        }

        private void AddControl(TreeNodeCollection parentNode, WebControl control)
        {
            var tn = new TreeNode();
            string text = control.Tag;

            var values = control.Attrs(KnownAttribute.ID, KnownAttribute.Name, KnownAttribute.ClassName, KnownAttribute.Src, KnownAttribute.Href);

            if (!string.IsNullOrEmpty(values[0]))
                text += GetNonEmptyString(values[0], " id=\"{0}\"");
            else
            {
                text += GetNonEmptyString(values[1], " name=\"{0}\"");
                text += GetNonEmptyString(values[2], " class=\"{0}\"");
                text += GetNonEmptyString(values[3], " src=\"{0}\"");
                text += GetNonEmptyString(values[4], " href=\"{0}\"");
            }

            tn.Nodes.Add(string.Empty).Tag = _fakeNodeTag;
            tn.Text = SafeLen(text, 80);
            tn.ToolTipText = text;
            tn.Tag = control;
            parentNode.Add(tn);
        }

        private static string GetNonEmptyString(string value, string formatter)
        {
            if (!string.IsNullOrEmpty(value))
                return string.Format(formatter, value);

            return string.Empty;
        }

        private void AddControls(TreeNodeCollection parentNode, IEnumerable<WebControl> controls)
        {
            foreach (var control in controls)
            {
                AddControl(parentNode, control);
            }
        }
        
        private void AddTaskLog(string s)
        {
            TextBoxLogs.Text += s + "\r\n";
        }

        private string FigureOutSelector(TreeNode node, WebControl control)
        {
            var foundgenerator = _selectorFactory.GenerateSelector(control);

            if (foundgenerator != null)
                return foundgenerator.Selector;

            // try adding parent IDs to make unique
            var parNode = node.Parent;
            var parControl = GetWebControl(parNode);

            var selector = control.Tag;

            while (parControl != null && parControl.Tag.ToUpper() != "HTML")
            {
                var newselector = _selectorFactory.GenerateSelector(parControl);

                if (newselector == null)
                    break;

                selector = newselector.Selector + " > " + selector;

                var foundControls = control.Page.GetControls<WebControl>(selector);
                if (foundControls.Count == 1)
                    return selector;

                parNode = parNode.Parent;
                parControl = GetWebControl(parNode);
            }

            // could not find a valid selector. Return default :eq version
            return control.Selector;
        }

        private void FillInDetails(TreeNode node)
        {
            WebControl control = GetWebControl(node);
            if (control == null)
            {
                TextBoxAttributes.Text = string.Empty;
            }
            else
            {
                string attributes = string.Empty;

                TextBoxSelector.Text = FigureOutSelector(node, control);
                txtCodeWithin.Text = TextBoxSelector.Text;

                string tag = control.Tag;
                attributes += "Tag: " + tag + "\r\n";

                var showAtts = new List<KnownAttribute>();
                foreach (KnownAttribute att in Enum.GetValues(typeof(KnownAttribute)))
                {
                    if (att != KnownAttribute.Tag && att != KnownAttribute.InnerText)
                        showAtts.Add(att);
                }

                var attrs = control.Attrs(showAtts.ToArray());

                for (var i = 0; i < showAtts.Count; i++)
                {
                    var attr = attrs[i];
                    if (!string.IsNullOrEmpty(attr))
                        attributes += showAtts[i] + ": " + attr + "\r\n";
                }

                TextBoxAttributes.Text = attributes;
            }
        }

        private void FillNode(TreeNode node)
        {
            if (node != null && node.Nodes.Count == 1 && node.Nodes[0].Tag == _fakeNodeTag)
            {
                var delnode = node.Nodes[0];

                ParseElements(node);

                delnode.Remove();
            }
        }

        private static WebControl GetWebControl(TreeNode node)
        {
            if (node != null)
                return node.Tag as WebControl;
            return null;
        }

        private WebControl GetSelectedWebControl()
        {
            return GetWebControl(TreeViewElements.SelectedNode);
        }

        private bool IsMatchingNode(TreeNode node, Regex regex)
        {
            WebControl ctrl = GetWebControl(node);

            if (ctrl == null)
                return false;

            bool ismatch = false;

            if (regex.IsMatch(ctrl.ID) ||
                regex.IsMatch(ctrl.Name) ||
                regex.IsMatch(ctrl.Attr(KnownAttribute.Src)) ||
                regex.IsMatch(ctrl.Attr(KnownAttribute.Href)))
            {
                ismatch = true;
            }
            else
            {
                var tag = ctrl.Tag;
                if (tag == "A" || tag == "DIV" || tag == "SPAN")
                {
                    ismatch = regex.IsMatch(ctrl.InnerText);
                }
            }

            if (ismatch)
            {
                TreeViewElements.SelectedNode = node;
                return true;
            }

            return false;
        }

        private void OhCrap(string message, Exception e)
        {
            MessageBox.Show(this, (message ?? "An error occurred during the request:") + "\n" + e.Message);
        }

        private void OpenUrl(string url)
        {
            try
            {
                var waitCursor = new WaitCursor();
                if (_testShell.TestSession != null && _testShell.TestSession.IsStarted)
                    _testShell.TestSession.Browser.Navigate(url);
                else
                {
                    _testShell.StartupUrl = url;
                    _testShell.Start();
                }

                SyncForm();
                waitCursor.Restore();

                if (!Properties.Settings.Default.URLs.Contains(url))
                {
                    Properties.Settings.Default.URLs.Add(url);
                    Properties.Settings.Default.Save();
                }
            }
            catch (Exception ex)
            {
                OhCrap("Error opening URL " + url, ex);
            }
        }

        private void ParseElements()
        {
            var wait = new WaitCursor();
            ComboBoxUrl.Text = _testShell.Browser.Page.Location;
            TreeViewElements.Nodes.Clear();
            AddControls(TreeViewElements.Nodes, _testShell.Page.GetControls<WebControl>("HTML"));
            AddSelectors();
            wait.Restore();
        }

        /// <summary>
        /// Expands a treeview node by finding the list of controls contained within the treenode
        /// </summary>
        /// <param name="parentNode">the node to expand</param>
        private void ParseElements(TreeNode parentNode)
        {
            var wait = new WaitCursor();
            var ctrl = GetWebControl(parentNode);

            AddControls(parentNode.Nodes, ctrl.GetControls<WebControl>("> *"));
            wait.Restore();
        }

        /// <summary>
        /// Adds the custom selectors to the treeview
        /// </summary>
        private void AddSelectors()
        {
            foreach (DataRow dr in _dtSelectors.Rows)
            {
                var selector = dr[1].ToString();
                var controls = _testShell.Page.GetControls<WebControl>(selector);

                AddControlsForSelector(selector, controls);
                dr[0] = controls.Count;
            }
        }

        /// <summary>
        /// Refreshes the selector grid
        /// </summary>
        private void RefreshSelectorGrid()
        {
            _autocomplete.Clear();
            foreach (DataRow dr in _dtSelectors.Rows)
            {
                var selector = dr[1].ToString();
                _autocomplete.Add(selector);
                var controls = _testShell.Page.GetControls<WebControl>(selector);
                dr[0] = controls.Count;
            }
        }

        /// <summary>
        /// Gets a string of at most maxlen characeters
        /// </summary>
        /// <param name="str">The string</param>
        /// <param name="maxlen">the maximum length of the string</param>
        /// <returns>A string of at most maxlen characters</returns>
        private static string SafeLen(string str, int maxlen)
        {
            if (str.Length <= maxlen)
                return str;

            return str.Substring(0, maxlen);
        }

        private bool SearchThroughChildren(TreeNode node, Regex regex)
        {
            FillNode(node);

            if (node.Nodes.Count > 0)
                return SearchThroughSiblings(node.Nodes[0], regex);

            return false;
        }

        private bool SearchThroughSiblings(TreeNode node, Regex regex)
        {
            for (; node != null; node = node.NextNode)
            {
                if (IsMatchingNode(node, regex))
                    return true;

                if (SearchThroughChildren(node, regex))
                    return true;
            }

            return false;
        }

        private void SelectorStatus(string msg)
        {
            lblSelectorStatus.Text = msg;
        }

        /// <summary>
        /// Displays a node into the browser
        /// </summary>
        /// <param name="node">the tree node</param>
        private static void ShowNode(TreeNode node)
        {
            var ctrl = GetWebControl(node);
            if (ctrl != null)
            {
                ctrl.Highlight();
            }
        }

        private void ShowSelector()
        {
            var selector = TextBoxSelector.Text;

            if (string.IsNullOrEmpty(selector))
                MessageBox.Show("You must enter a selector");
            else
            {            
                SelectorStatus("Selector: " + selector);
                try
                {
                    var controls = _testShell.Browser.Page.GetControls<WebControl>(selector);

                    var found = false;
                    foreach (DataRow existingdr in _dtSelectors.Rows)
                    {
                        if (existingdr[1].ToString() == selector)
                        {
                            existingdr[0] = controls.Count;
                            found = true;

                            break;
                        }
                    }

                    if (!found)
                    {
                        var dr = _dtSelectors.NewRow();
                        dr["Count"] = controls.Count;
                        dr["Selector"] = selector;
                        _dtSelectors.Rows.Add(dr);
                    }

                    AddControlsForSelector(selector, controls);

                    if (controls.Count == 0)
                        SelectorStatus("Not found!");
                    else
                        SelectorStatus("Found " + controls.Count + " matching element" + (controls.Count == 1 ? string.Empty : "s"));
                }
                catch (Exception e)
                {
                    SelectorStatus("Exception: " + e.Message);
                }
            }
        }
        
        public void SyncForm()
        {
            try
            {
                ParseElements();
                RefreshSelectorGrid();
                SplitContainer1.Enabled = true;
            }
            catch (Exception ex)
            {
                AddTaskLog("Exception: " + ex.Message);
            }
        }
        
        private IEnumerable<WebControl> GetListOfControls(string within)
        {
            var parentcontrol = TestShell.Page.GetControl(within);

            foreach (var control in parentcontrol.GetControls<WebControl>("[id]"))
                yield return control;
            foreach (var control in parentcontrol.GetControls<WebControl>("a[href]:not([id])"))
                yield return control;
            foreach (var control in parentcontrol.GetControls<WebControl>("[name!='']:not([id])"))
                yield return control;
            foreach (var control in parentcontrol.GetControls<WebControl>("[class!='']:not([id])"))
                yield return control;
        }

        private void btnFindAll_Click(object sender, EventArgs e)
        {
            var wait = new WaitCursor();

            lstControlsToGenerate.Items.Clear();

            var items = new List<CBListItem>();

            var dict = new Dictionary<string, bool>();

            foreach (var ctrl in GetListOfControls(txtCodeWithin.Text))
            {
                var sel = _selectorFactory.GenerateSelector(ctrl);

                if (sel != null && sel.Selector != ctrl.Selector)
                {
                    var key = sel.Selector + "." + sel.Name;
                    if (!dict.ContainsKey(key))
                    {
                        items.Add(new CBListItem(sel));
                        dict.Add(key, true);
                    }
                }
            }

            dict.Clear();
            items.OrderBy(x => x.Display).ToList().ForEach(item =>
                                                               {
                                                                   var disp = item.Display;
                                                                   var key = disp.ToLower();
                                                                   var checkit = true;

                                                                   if (dict.ContainsKey(key))
                                                                       checkit = false;
                                                                   else
                                                                   {
                                                                       dict.Add(key, true);

                                                                       if (disp == "[NAME]" || disp.StartsWith("Hidden"))
                                                                           checkit = false;
                                                                   }


                                                                   lstControlsToGenerate.Items.Add(item, checkit);
                                                               });

            if (items.Count > 0)
                btnGeneratePageCode.Enabled = true;
        }

        private void btnGeneratePageCode_Click(object sender, EventArgs e)
        {
            var c = lstControlsToGenerate.CheckedItems.Count;

            if (c == 0)
            {
                MessageBox.Show("You need to select something.");
                return;
            }

            var markup = string.Empty;

            if (TextBoxControlName.Enabled)
            {
                var classname = TextBoxControlName.Text + "Control";
                var variablename = "_" + classname.Substring(0, 1).ToLower() + classname.Substring(1);
                markup += "private " + classname + " " + variablename + ";\r\n\r\n";

                markup += "/// <summary>\r\n";
                markup += "/// Gets the " + classname + " control\r\n";
                markup += "/// </summary>\r\n";
                markup += "public " + classname + " " + TextBoxControlName.Text + "\r\n";
                markup += "{\r\nget\r\n{\r\n";
                markup += "if (" + variablename + " == null)\r\n";
                markup += variablename + " = new " + classname + "(this);\r\n";
                markup += "return " + variablename + ";\r\n";
                markup += "}\r\n}\r\n\r\n";

                markup += "/// <summary>\r\n";
                markup += "/// The " + TextBoxControlName.Text + " control\r\n";
                markup += "/// </summary>\r\n";
                markup += "public class " + classname + " : WebControl\r\n";
                markup += "{\r\n";
                markup += "/// <summary>\r\n";
                markup += "/// Initializes a new instance of the <see cref=\"" + classname + "\"/> class.\r\n";
                markup += "/// </summary>\r\n";
                markup += "/// <param name=\"page\">The page.</param>\r\n";
                markup += "public " + classname + "(WebControlContainer page)\r\n";
                markup += ": base(page, \"" + txtCodeWithin.Text + "\")\r\n";
                markup += "{\r\n";
                markup += "}\r\n\r\n";

            }

            for (var x = 0; x < c; x++)
            {
                var item = (CBListItem)lstControlsToGenerate.CheckedItems[x];

                markup += item.Selector.Code + "\r\n\r\n";
            }

            if (TextBoxControlName.Enabled)
                markup += "}\r\n\r\n";

            Clipboard.SetDataObject(markup, true);
            MessageBox.Show("The code for all selected items has been copied to your clipboard.");
        }

        private void chkToGenerate_Click(object sender, EventArgs e)
        {
        }

        private void chkToGenerate_SelectedIndexChanged(object sender, EventArgs e)
        {
            chkToGenerateSelectTimer.Stop();
            chkToGenerateSelectTimer.Start();
        }

        private void chkToGenerateSelectTimer_Tick(object sender, EventArgs e)
        {
            chkToGenerateSelectTimer.Stop();
            var item = lstControlsToGenerate.SelectedItem as CBListItem;
            if (item == null)
                return;

            var ctrl = TestShell.Page.GetControl(item.Selector.Selector);
            if (ctrl.Exists)
                ctrl.Highlight();
        }

        public class CBListItem
        {
            public CBListItem(SelectorGeneratorFactory.IGeneratedSelector sel)
            {
                Selector = sel;
            }

            public string Display
            {
                get
                {
                    return Selector.Name;
                }
            }

            public SelectorGeneratorFactory.IGeneratedSelector Selector
            {
                get;
                private set;
            }

            public override string ToString()
            {
                return Display + " (" + Selector.Selector + ")";
            }
        }

        private void CheckBoxCodeAsControl_CheckedChanged(object sender, EventArgs e)
        {
            TextBoxControlName.Enabled = CheckBoxCodeAsControl.Checked;
        }

        private void ButtonAttach_Click(object sender, EventArgs e)
        {
            AttachToSelenium();
        }

        /// <summary>
        /// Attaches to an existing selenium interface
        /// </summary>
        private void AttachToSelenium()
        {
            var form = new FormAttach();
            form.ShowDialog(this);

            if (form.OK)
            {
                _testShell.AttachToSession(form.SessionId);
                SyncForm();
            }
        }
    }
}
